#include "Drawer.h"

#include <cassert>
#include <SDL.h>

#include "main/Globals.h"
#include "math/MathConstants.h"
#include "utils/Error.h"

namespace {
    // Stores in a SDL_Rect the rectangle where we
    // should draw the SDL_Texture, taking into account
    // the center position.
    void rectsWhereDraw(const DynamicData * drawData,
                        TextureData* const textures,
                        SDL_Rect * const rectangles,
                        const uint32_t numElems)
    {
        // PARALLEL_FOR
        for (uint32_t rectIndex = 0; rectIndex < numElems; ++rectIndex) {
            const float& centerPosX = *drawData[rectIndex].mCenterPosX;
            const float& centerPosY = *drawData[rectIndex].mCenterPosY;
            SDL_Texture* &texture = textures[rectIndex].mTexture;
            SDL_Rect* texRect = textures[rectIndex].mRect;
            assert(texture);
            int width;
            int height;

            // If texture rectangle is nullptr, then we should 
            // consider complete texture dimensions
            if (!texRect) {
                const int result = 
                    SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
                if (result != 0) {
                    printError("SDL_QueryTexture failed");      
                    return;
                }
            } else {
                // Else we should consider texture rectangle
                width = texRect->w;
                height = texRect->h;
            }
                
            SDL_Rect& rectangle = rectangles[rectIndex];
            rectangle.x = static_cast<int> (centerPosX) - (width / 2);
            rectangle.y = static_cast<int> (centerPosY) - (height / 2);
            rectangle.w = width;
            rectangle.h = height;
        }
    }

    // Stores in a SDL_Rect the rectangle where we
    // should draw the SDL_Texture, taking into account
    // the center position.
    void rectsWhereDraw(const StaticData * drawData,
                        TextureData* const textures,
                        SDL_Rect * const rectangles,
                        const uint32_t numElems)
    {
        // PARALLEL_FOR
        for (uint32_t rectIndex = 0; rectIndex < numElems; ++rectIndex) {
            const float& centerPosX = drawData[rectIndex].mCenterPosX;
            const float& centerPosY = drawData[rectIndex].mCenterPosY;
            SDL_Texture* &texture = textures[rectIndex].mTexture;
            SDL_Rect* texRect = textures[rectIndex].mRect;
            assert(texture);
            int width;
            int height;

            // If texture rectangle is nullptr, then we should 
            // consider complete texture dimensions
            if (!texRect) {
                const int result = 
                    SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
                if (result != 0) {
                    printError("SDL_QueryTexture failed");      
                    return;
                }
            } else {
                // Else we should consider texture rectangle
                width = texRect->w;
                height = texRect->h;
            }

            SDL_Rect& rectangle = rectangles[rectIndex];
            rectangle.x = static_cast<int> (centerPosX) - (width / 2);
            rectangle.y = static_cast<int> (centerPosY) - (height / 2);
            rectangle.w = width;
            rectangle.h = height;
        }
    }

    // Stores in a SDL_Rect the rectangle where we
    // should draw the SDL_Texture
    void rectsWhereDraw(TextureData* const textures,
                        SDL_Rect * const rectangles,
                        const uint32_t numElems)
    {
        // PARALLEL_FOR
        for (uint32_t rectIndex = 0; rectIndex < numElems; ++rectIndex) {
            SDL_Texture* &texture = textures[rectIndex].mTexture;
            SDL_Rect* texRect = textures[rectIndex].mRect;
            assert(texture);
            int width;
            int height;

            // If texture rectangle is nullptr, then we should 
            // consider complete texture dimensions
            if (!texRect) {
                const int result = 
                    SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
                if (result != 0) {
                    printError("SDL_QueryTexture failed");      
                    return;
                }
            } else {
                // Else we should consider texture rectangle
                width = texRect->w;
                height = texRect->h;
            }

            SDL_Rect& rectangle = rectangles[rectIndex];
            rectangle.x = 0;
            rectangle.y = 0;
            rectangle.w = width;
            rectangle.h = height;
        }
    }
}

DynamicData::DynamicData(const float* centerPosX /*= nullptr*/,
                         const float* centerPosY /*= nullptr*/,
                         const float* orientation /*= nullptr*/)
                         : mCenterPosX(centerPosX)
                         , mCenterPosY(centerPosY)
                         , mOrientation(orientation)
{

}

StaticData::StaticData(const float centerPosX /*= 0.0f*/,
                       const float centerPosY /*= 0.0f*/,
                       const float orientation /*= 0.0f*/)
                       : mCenterPosX(centerPosX)
                       , mCenterPosY(centerPosY)
                       , mOrientation(orientation)
{

}

TextureData::TextureData(SDL_Texture* texture /*= nullptr*/, SDL_Rect* rect /*= nullptr*/)
    : mTexture(texture)
    , mRect(rect)
{
    // If rect is nullptr, then entire texture should be drawn
    // Otherwise, we make a copy of the rect.
    if (rect) {
        assert(texture);

        mRect = new SDL_Rect;
        *mRect = *rect;

        // Check rect is not out of bounds
        int w;
        int h;
        const int result = SDL_QueryTexture(mTexture, 0, 0, &w, &h);
        if (result != 0) {
            printError("SDL_QueryTexture failed");
            return;
        }

        assert(mRect->x < w);
        assert(mRect->x + mRect->w <= w);
        assert(mRect->y < h);
        assert(mRect->y + mRect->h <= h);
    }
}

TextureData::~TextureData() {
    if (mRect) {
        delete mRect;
    }
}

void DynamicData::set(const float& centerPosX,
                      const float& centerPosY,
                      const float& orientation) 
{
    mCenterPosX = &centerPosX;
    mCenterPosY = &centerPosY;
    mOrientation = &orientation;
}

void StaticData::set(const float centerPosX,
                     const float centerPosY,
                     const float orientation) 
{
    mCenterPosX = centerPosX;
    mCenterPosY = centerPosY;
    mOrientation = orientation;
}

void TextureData::updateTexture(SDL_Texture* tex) {
    assert(tex);
    mTexture = tex;

    if (mRect) {
        delete mRect;
        mRect = nullptr;
    }
}

void TextureData::updateRect(const SDL_Rect& rect) {
    assert(mTexture);

    // If rect is nullptr, then entire texture should be drawn
    // Otherwise, we make a copy of the rect.
    if (!mRect) {
        mRect = new SDL_Rect;        
    }

    *mRect = rect;

    // Check rect is not out of bounds
    int w;
    int h;
    const int result = SDL_QueryTexture(mTexture, 0, 0, &w, &h);
    if (result != 0) {
        printError("SDL_QueryTexture failed");
        return;
    }

    assert(rect.x < w);
    assert(rect.x + rect.w <= w);
    assert(rect.y < h);
    assert(rect.y + rect.h <= h);
}

void drawTextures(const DynamicData * const data,
                  TextureData* const textures,
                  const uint32_t numElems)
{
    assert(data);
    assert(textures);
    assert(numElems > 0);

    SDL_Rect* rectangles = new SDL_Rect[numElems];
    rectsWhereDraw(data, textures, rectangles, numElems);

    for (uint32_t rectIndex = 0; rectIndex < numElems; ++rectIndex) {
        const SDL_Rect& rectangle = rectangles[rectIndex];
        SDL_Texture* &texture = textures[rectIndex].mTexture;
        SDL_Rect* texRect = textures[rectIndex].mRect;

        // SDL_RenderCopyEx considers:
        //    - Rotation is from y axis to -x axis (multiply by -1)
        //    - Rotation is in euler angles (transform radians to euler)
        const float orientation = -1.0f * *data[rectIndex].mOrientation * RAD2DEG;

        SDL_RenderCopyEx(Globals::gRenderer, 
            texture, 
            texRect, 
            &rectangle, 
            orientation, 
            nullptr, 
            SDL_FLIP_NONE);
    }

    delete[] rectangles;
}

void drawTextures(const StaticData * const data,
                  TextureData* const textures,
                  const uint32_t numElems)
{
    assert(data);
    assert(textures);
    assert(numElems > 0);

    SDL_Rect* rectangles = new SDL_Rect[numElems];
    rectsWhereDraw(data, textures, rectangles, numElems);

    for (uint32_t rectIndex = 0; rectIndex < numElems; ++rectIndex) {
        const SDL_Rect& rectangle = rectangles[rectIndex];
        SDL_Texture* &texture = textures[rectIndex].mTexture;
        SDL_Rect* texRect = textures[rectIndex].mRect;

        // SDL_RenderCopyEx considers:
        //    - Rotation is from y axis to -x axis (multiply by -1)
        //    - Rotation is in euler angles (transform radians to euler)
        const float orientation = -1.0f * data[rectIndex].mOrientation * RAD2DEG;

        SDL_RenderCopyEx(Globals::gRenderer, 
            texture, 
            texRect, 
            &rectangle, 
            orientation, 
            nullptr, 
            SDL_FLIP_NONE);
    }

    delete[] rectangles;
}

void drawTextures(TextureData* const textures, const uint32_t numElems) {
    assert(textures);
    assert(numElems > 0);

    SDL_Rect* rectangles = new SDL_Rect[numElems];
    rectsWhereDraw(textures, rectangles, numElems);

    for (uint32_t rectIndex = 0; rectIndex < numElems; ++rectIndex) {
        const SDL_Rect& rectangle = rectangles[rectIndex];
        SDL_Texture* &texture = textures[rectIndex].mTexture;
        SDL_Rect* texRect = textures[rectIndex].mRect;

        // SDL_RenderCopyEx considers:
        //    - Rotation is from y axis to -x axis (multiply by -1)
        //    - Rotation is in euler angles (transform radians to euler)
        const float orientation = 0.0f;

        SDL_RenderCopyEx(Globals::gRenderer, 
            texture, 
            texRect, 
            &rectangle, 
            0.0, 
            nullptr, 
            SDL_FLIP_NONE);
    }

    delete[] rectangles;
}